前幾天從使用者痛點出發,用 Stitch Designer 產出了 App 的 UI 初稿,接著在 Figma 上將設計元素系統化,建立了顏色的色階、定義了字體等級,也規範了間距和圓角。
今天,就要將這些視覺骨架變成實際的 Flutter 程式碼!這一步是從設計到開發的關鍵轉折,透過程式化的方式來管理視覺規範,能大幅提升開發效率,並確保 App 的視覺呈現一致且易於維護。
在開始寫程式之前,先建立一個清晰的資料夾結構。這能幫助我們有條理地組織程式碼,避免所有東西都堆在 main.dart
裡。
可以這樣規劃:
lib/
├── main.dart
└── theme/
├── app_colors.dart
├── app_typography.dart
├── app_spacing.dart
├── app_radius.dart
└── app_theme.dart
assets/
└── icons/
app_colors.dart
:專門用來存放我們定義的顏色。(使用 app_ 前綴是為了讓這些檔案在專案變大時,能更清楚地和第三方套件的檔案區分開來。)app_typography.dart
:專門用來存放我們的字型樣式。app_spacing.dart
:專門用來存放間距的數值。app_radius.dart
:專門用來存放圓角的數值。app_theme.dart
:將顏色、字型、間距和圓角整合,建立完整的 ThemeData
。assets/icons/
:放置我們所有 SVG 格式的圖示。這樣的分層結構,讓未來修改顏色或字型時,只需要在對應的檔案裡調整,就能輕鬆更新整個 App 的視覺。
在 Day 3 定義了品牌色、中性色和狀態色,現在我們要把它們寫成程式碼。在 Flutter 中,可以使用 ColorScheme 和自訂的類別來管理這些顏色。
首先,在 lib/theme/app_colors.dart
中,定義我們需要的顏色。
import 'package:flutter/material.dart';
// 根據 Figma 截圖調整顏色定義
// Primary 顏色階
class AppPrimaryColors {
static const Color primary50 = Color(0xFFEDF2F8);
// 略
}
// Grayscale 顏色階
class AppGrayscaleColors {
static const Color gray50 = Color(0xFFF8F8F8);
// 略
}
// 狀態色
class AppStatusColors {
static const Color success = Color(0xFFE6F7E9);
// 略
}
在 Flutter 中,TextTheme 幫助我們定義各種字體樣式,例如標題、內文等,這和我們在 Figma 中定義的字體等級(Hierarchy)完全對應。
在 lib/theme/app_typography.dart
中,我們可以將這些樣式定義出來。
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
class AppTypography {
// 建立 TextTheme,並使用 Google Fonts 來載入字型
// 由於設計稿在 Display, Headline, Title, Body, Label 各只有一種樣式,
// 因此我們只針對這些樣式進行定義,並將其賦予給 `*Large` 屬性。
static final TextTheme _textTheme = GoogleFonts.plusJakartaSansTextTheme(
const TextTheme(
// Display
displayLarge: TextStyle(fontWeight: FontWeight.bold, fontSize: 57),
// Headline
// 略
),
);
static TextTheme get textTheme => _textTheme;
}
在 lib/theme/app_spacing.dart
中,我們可以使用自訂的類別來管理這些間距。
import 'package:flutter/material.dart';
// 使用 4 的倍數來定義標準化間距
class AppSpacing {
static const double extraSmall = 4.0;
//略
}
就像間距一樣,我們也將定義一組標準化的圓角數值,來確保所有元件的圓角風格一致。在 lib/theme/app_radius.dart
中,建立一個新的類別來存放這些數值。
import 'package:flutter/material.dart';
// 使用 4 的倍數來定義標準化間距
class AppRadius {
static const double s = 4.0;
//略
}
把剛剛定義好的顏色、字型、間距和圓角,整合到一個完整的 ThemeData
物件中,並在 main.dart
裡應用它。
接著,在 lib/theme/app_theme.dart
中,加入 ThemeData
的定義:
import 'package:flutter/material.dart';
import 'app_colors.dart';
import 'app_typography.dart';
import 'app_spacing.dart';
import 'app_radius.dart';
final ColorScheme _appColorScheme = const ColorScheme.light().copyWith(
primary: AppPrimaryColors.primary800, // 選用 primary 的深色階作為主要顏色
onPrimary: AppColors.onPrimary,
// 略
);
// 定義 App 的主題
final ThemeData appTheme = ThemeData(
// 設置 ColorScheme
colorScheme: _appColorScheme,
// 設置 TextTheme
textTheme: AppTypography.textTheme,
// 其他主題屬性...
);
現在,把剛剛定義好的顏色、字型、間距和圓角,整合到一個完整的 ThemeData 物件中,並在 main.dart 裡應用它。
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
// 將我們建立的 appTheme 應用到整個 App
theme: appTheme,
home: const MyHomePage(),
);
}
}
使用方式
final theme = Theme.of(context);
final textTheme = theme.textTheme;
final colorScheme = theme.colorScheme;
Center(
child: Container(
// 示範:直接引用間距
padding: const EdgeInsets.symmetric(
horizontal: AppSpacing.medium,
vertical: AppSpacing.small,
),
decoration: BoxDecoration(
// 示範:直接引用顏色
color: AppStatusColors.info,
// 示範:直接引用圓角
borderRadius: BorderRadius.circular(AppRadius.medium),
),
child: Text(
'這是一個提示訊息!',
// 示範:使用主題文字
style: textTheme.bodyMedium?.copyWith(
// 示範:使用主題顏色
color: colorScheme.onPrimary,
),
),
),
),
今日成果:
本日總結:
今天成功將 Day 3 的設計系統,透過 Flutter 的 ThemeData 轉換成可用的程式碼。現在,不需要在每個元件上寫死顏色、字型、間距和圓角,只需透過 Theme.of(context) 或是直接使用我們的自訂類別,就能輕鬆套用,大大提高了開發效率和維護性。
下一篇預告:
視覺骨架已經就緒,接下來我們要回到設計稿,開始建立元件。我會用各種 UI 元件當例子,說明如何將這些視覺規範應用到更複雜的 UI 元件上,並讓它們變得可重複使用。